#include <basic/errcode.h>
#include <basic/cnfg.h>

#define	_INIT_UNITS					5
#define	_NEXT_UNITS					5

struct _property
{/*{{{*/
    enum cnfg_property_type type;
    char *name;
    char *value;
};/*}}}*/

struct _section
{/*{{{*/
    char *name;

    int property_num;
    int property_alloc_num;
    struct _property *property;
};/*}}}*/

struct _cnfg 
{/*{{{*/
    int section_num;
    int section_alloc_num;
    struct _section *section;
};/*}}}*/

static int _get_section(CNFG cnfg, char *section_name,
        unsigned int case_sensitive)
{/*{{{*/
    char name[NAME_SIZE+1];
    int section_idx;

    if(!section_name)
        return(0);

    strcpy(name, section_name);
    trim_head_space(name);
    trim_tail_space_notnull(name);

    for(section_idx=1; section_idx<cnfg->section_num; ++section_idx)
    {
        struct _section *section=&cnfg->section[section_idx];
        int res;

        if(case_sensitive)
            res=strcmp(section->name, name);
        else
            res=strcasecmp(section->name, name);
        if(res == 0)
           return(section_idx);
    }
    return(-1);
}/*}}}*/

static int _get_property(CNFG cnfg, int section_idx,
        char *property_name, unsigned int case_sensitive)
{/*{{{*/
    struct _section *section;
    int property_idx;
    struct _property *property;
    int res;

    section=&cnfg->section[section_idx];
    for(property_idx=0; property_idx<section->property_num;
            ++property_idx)
    {
        property=&section->property[property_idx];
        if(case_sensitive)
            res=strcmp(property->name, property_name);
        else
            res=strcasecmp(property->name, property_name);
        if(res == 0)
            return(property_idx);
    }
    return(-1);
}/*}}}*/

static void _trim_line(char *line, int line_size)
{/*{{{*/
    int len;
    unsigned int quote;
    int i;

    line[line_size-1]=ZERO;
    len=strlen(line);  len--;
    if(line[len] == RTN)  line[len]=ZERO;
    
    quote=0;
    i=0;
    while(line[i])
    {
        if(line[i] == '"')  quote=!quote;
        if(!quote)
        {
            if(line[i] == TAB)
                line[i]=SPACE;
            else if(line[i] == '#')
            {   line[i]=ZERO; break;   }
        }
        i++;
    }
    trim_head_space(line);
    trim_tail_space(line);
}/*}}}*/

static void _trim_quote(char *item)
{/*{{{*/
    int len;
    int trim, orig;

    len=strlen(item);
    orig=trim=0;
    while(orig < len)
    {
        if(item[orig] != '"')
            item[trim++]=item[orig];
        orig++;
    }
    item[trim]=ZERO;
}/*}}}*/

static int _add_section(struct _cnfg *cnfg, char *section_name)
{/*{{{*/
    int section_idx;
    struct _section *section;

    if(cnfg->section_num == cnfg->section_alloc_num)
        EXPAND_ARRAY_RETERR(cnfg->section, struct _section,
                _NEXT_UNITS, cnfg->section_alloc_num,
                RET_ERR_ALLOC);

    section_idx=cnfg->section_num;
    section=&cnfg->section[section_idx];
    if(section_name)
        section->name=strdup(section_name);
    else
        section->name=strdup(SPACES);
    if(!section->name)
        return(RET_ERR_ALLOC);
    ALLOC_ARRAY_RETERR(section->property, struct _property,
            _INIT_UNITS, section->property_alloc_num,
            RET_ERR_ALLOC);
    section->property_num=0;
    ++cnfg->section_num;

    return(section_idx);
}/*}}}*/

static int _add_property(struct _cnfg *cnfg, int section_idx, char *line)
{/*{{{*/
    int rtn=0;
    int line_size=strlen(line);
    int orig, trim;
    unsigned int quote;
    char *p_equal;
    char name[LONG_FILE_NM_SIZE+1];
    char value[LONG_FILE_NM_SIZE+1];

    // 过滤空格
    quote=0;
    orig=trim=0;
    while(orig < line_size)
    {
        if(line[orig] == '"')  quote=!quote;
        if(!quote)
        {
            if(line[orig] != SPACE)
                line[trim++]=line[orig];
        }
        else
        {
            line[trim++]=line[orig];
        }
        ++orig;
    }
    line[trim]=ZERO;

    // 判断类型 
    p_equal=strchr(line, '=');
    if(p_equal)
    {
        int name_size=p_equal-line;
        strncpy(name, line, name_size);
        name[name_size]=ZERO;

        if(*(p_equal+1) == '(')
        {
           if(line[trim-1] == ')')
           {
               char **array;
               int array_num;

               rtn=CNFG_PROPERTY_COMPOSITE;
               strcpy(value, p_equal+2);
               value[strlen(p_equal+2)-1]=ZERO;
               array_num=str_split(value, ';', &array);
               if(array_num > 0)
               {
                   int i;
                   for(i=0; i<array_num; ++i)
                   {
                       int num;
                       char **a;
                       num=str_split(array[i], '=', &a);
                       if(num > 0)
                       {
                           FREE_PTR_ARRAY(a, num);
                       }
                       if(num != 1 && num != 2)
                       {
                           rtn=-1;
                           break;
                       }
                   }
                   FREE_PTR_ARRAY(array, array_num);
               }
               else
                   rtn=-1;
           }
           else
               rtn=-1;
        }
        else
        {
            strcpy(value, p_equal+1);
            _trim_quote(value);
            rtn=CNFG_PROPERTY_NORMAL;
        }
    }
    else
    {
        strcpy(name, line);
        CLR_BUF(value);
        rtn=CNFG_PROPERTY_SIMPLE;
    }
    
    if(rtn >= CNFG_PROPERTY_SIMPLE)
    {
        struct _section *section=&cnfg->section[section_idx];
        int property_idx;
        struct _property *property;

        if(section->property_num == section->property_alloc_num)
            EXPAND_ARRAY_RETERR(section->property, struct _property,
                    _NEXT_UNITS, section->property_alloc_num,
                    RET_ERR_ALLOC);

        property_idx=section->property_num;
        property=&section->property[property_idx];
        property->type=rtn;
        property->name=property->value=NULL;
        if(!(property->name=strdup(name)) ||
                (!BUF_IS_CLR(value) && !(property->value=strdup(value))))
        {
            FREE(property->name);
            FREE(property->value);
            return(RET_ERR_ALLOC);
        }
        ++section->property_num;
    }

    return(rtn);
}/*}}}*/

int cnfg_load(char *cnfg_name, CNFG *pcnfg)
{/*{{{*/
    FILE *fp=(FILE *)NULL;
    CNFG cnfg=(CNFG)NULL;
    char line[BUFFER_SIZE];
    int lineno;
    int len;
    char section_name[100];
    int section_idx;
    int rtn;
    int res;

    if(!cnfg_name || !pcnfg)
        return(RET_ERR_PARA);

    if((fp=fopen(cnfg_name, "r")) == NULL)
        return(RET_ERR_FILE_OPEN);

    ALLOC_DOERR(cnfg, struct _cnfg,
            rtn=RET_ERR_ALLOC; goto GO_OUT);
    ALLOC_ARRAY_DOERR(cnfg->section, struct _section, _INIT_UNITS,
            cnfg->section_alloc_num,
            rtn=RET_ERR_ALLOC; goto GO_OUT);
    cnfg->section_num=0;
    // global section
    if((section_idx=_add_section(cnfg, NULL)) < 0)
    {   rtn=section_idx; goto GO_OUT;   }

    lineno=0;
    while(fgets(line, sizeof(line)-1, fp) != NULL)
    {
        lineno++;
        _trim_line(line, sizeof(line));
        if(BUF_IS_CLR(line))  continue;
        len=strlen(line);
        if(line[0] == '=' || line[len-1] == '=')    // =必须要有内容
        {   rtn=lineno; goto GO_OUT;   }

        // section
        if(sscanf(line, "[%[^]]", section_name) == 1)
        {
            trim_head_space(section_name);
            trim_tail_space(section_name);
            if(BUF_IS_CLR(section_name))  continue;
            if((section_idx=_add_section(cnfg, section_name)) < 0)
            {   rtn=section_idx; goto GO_OUT;   }
            continue;
        }

        // 按=分割
        res=_add_property(cnfg, section_idx, line);
        if(res < 0)
        {
            if(res == -1)
                rtn=lineno;
            else
                rtn=res;
            goto GO_OUT;
        }
    }   // line
    *pcnfg=cnfg;
    rtn=0;

GO_OUT:
    if(rtn) cnfg_free(cnfg);
    fclose(fp);
    return(rtn);
}/*}}}*/

void cnfg_free(CNFG cnfg)
{/*{{{*/
    int section_idx;

    if(!cnfg)
        return;

    for(section_idx=0; section_idx<cnfg->section_num; ++section_idx)
    {
        struct _section *section=&cnfg->section[section_idx];
        int property_idx;

        free(section->name);

        for(property_idx=0; property_idx<section->property_num;
            ++property_idx)
        {
            struct _property *property=&section->property[property_idx];
            FREE(property->name);
            FREE(property->value);
        }
        FREE(section->property);
    }
    FREE(cnfg->section);
    free(cnfg);
}/*}}}*/

int cnfg_property_array(CNFG cnfg, char *section_name, unsigned int flag,
        struct cnfg_property **parray)
{/*{{{*/
    int section_idx;
    struct _section *section=NULL;
    int property_idx;
    struct cnfg_property *array=NULL;
    int array_alloc_num=0;
    int array_num=0;
    int rtn;

    *parray=NULL;

    if(!cnfg || !parray)
        return(RET_ERR_PARA);
    if(section_name)
        if(BUF_IS_CLR(section_name) || strlen(section_name) > NAME_SIZE)
            return(RET_ERR_PARA);

    section_idx=_get_section(cnfg, section_name,
            (flag & FLAG_CASE_SENSITIVE)?1:0);
    if(section_idx == -1)
        return(RET_ERR_CNFG_SECTION_NOT_FOUND);
    section=&cnfg->section[section_idx];
    for(property_idx=0; property_idx<section->property_num;
            property_idx++)
    {
        struct _property *_property=
            &section->property[property_idx];
        struct cnfg_property *property;

        if(array_num == array_alloc_num)
            EXPAND_ARRAY_DOERR(array, struct cnfg_property,
                    _NEXT_UNITS, array_alloc_num,
                    rtn=RET_ERR_ALLOC; goto GO_OUT);
        property=&array[array_num];
        property->type=_property->type;
        strcpy(property->name, _property->name);
        if(_property->value)
            strcpy(property->value, _property->value);
        else
            CLR_BUF(property->value);
        ++array_num;
    }

    *parray=array;
    return(array_num);

GO_OUT:
    FREE(array);
    return(rtn);
}/*}}}*/

int cnfg_get_property(CNFG cnfg, char *section_name,
        char *property_name, unsigned int flag,
        struct cnfg_property *property)
{/*{{{*/
    unsigned int case_sensitive;
    int section_idx;
    int property_idx;
    struct _property *_property;

    if(!property)
        return(RET_ERR_PARA);
    property->type=0;
    CLR_BUF(property->name);
    CLR_BUF(property->value);
    if(!cnfg || !property_name)
        return(RET_ERR_PARA);
    if(section_name)
        if(BUF_IS_CLR(section_name) || strlen(section_name) > NAME_SIZE)
            return(RET_ERR_PARA);

    case_sensitive=(flag & FLAG_CASE_SENSITIVE)?1:0;
    section_idx=_get_section(cnfg, section_name, case_sensitive);
    if(section_idx == -1)
        return(RET_ERR_CNFG_SECTION_NOT_FOUND);
    property_idx=_get_property(cnfg, section_idx, property_name,
            case_sensitive);
    if(property_idx == -1)
        return(RET_ERR_CNFG_PROPERTY_NOT_FOUND);

    _property=&cnfg->section[section_idx].property[property_idx];
    property->type=_property->type;
    strcpy(property->name, _property->name);
    if(_property->value)
        strcpy(property->value, _property->value);
    else
        CLR_BUF(property->value);
    return(0);
}/*}}}*/

int cnfg_get_subproperty(CNFG cnfg, char *section_name,
        char *property_name, char *subproperty_name, unsigned int flag,
        struct cnfg_property *subproperty)
{/*{{{*/
    unsigned int case_sensitive;
    int section_idx;
    int property_idx;
    struct _property *_property;
    int subprop_idx;
    int subprop_num;
    char **subprop;

    if(!subproperty)
        return(RET_ERR_PARA);
    subproperty->type=0;
    CLR_BUF(subproperty->name);
    CLR_BUF(subproperty->value);
    if(!cnfg || !property_name || !subproperty_name)
        return(RET_ERR_PARA);
    if(section_name)
        if(BUF_IS_CLR(section_name) || strlen(section_name) > NAME_SIZE)
            return(RET_ERR_PARA);
    case_sensitive=(flag & FLAG_CASE_SENSITIVE)?1:0;

    section_idx=_get_section(cnfg, section_name, case_sensitive);
    if(section_idx == -1)
        return(RET_ERR_CNFG_SECTION_NOT_FOUND);
    property_idx=_get_property(cnfg, section_idx, property_name,
            case_sensitive);
    if(property_idx == -1)
        return(RET_ERR_CNFG_PROPERTY_NOT_FOUND);

    _property=&cnfg->section[section_idx].property[property_idx];
    if(_property->type != CNFG_PROPERTY_COMPOSITE)
        return(RET_ERR_CNFG_PROPERTY_NOT_COMPOSITE);
    subprop_num=str_split(_property->value, ';', &subprop);
    for(subprop_idx=0; subprop_idx<subprop_num; ++subprop_idx)
    {
        int num;
        char **a;
        int res;

        num=str_split(subprop[subprop_idx], '=', &a);
        if(case_sensitive)
            res=strcmp(a[0], subproperty_name);
        else
            res=strcasecmp(a[0], subproperty_name);
        if(res == 0)
        {
            strcpy(subproperty->name, a[0]);
            if(num == 1)
            {
                subproperty->type=CNFG_PROPERTY_SIMPLE;
                CLR_BUF(subproperty->value);
            }
            else
            {
                subproperty->type=CNFG_PROPERTY_NORMAL; 
                strcpy(subproperty->value, a[1]);
                _trim_quote(subproperty->value);
            }
            FREE_PTR_ARRAY(a, num);
            break;
        }
        FREE_PTR_ARRAY(a, num);
    }
    FREE_PTR_ARRAY(subprop, subprop_num);
    if(subprop_idx == subprop_num) 
        return(RET_ERR_CNFG_PROPERTY_NOT_FOUND);

    return(0);
}/*}}}*/
